
#region Imported Namespaces
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.WebControls;

using PHSRAG.Insight21.SearchContext;
using DataAccess = PHSRAG.Utility.DataAccess;
using PHSRAG.WebControls;
using SessionCachePolicy = PHSRAG.Insight21.Policy.SessionCachePolicy;
using Policy = PHSRAG.Insight21.Policy;
using UserContext = PHSRAG.Insight21.Context.UserContext;
using Calendar = PHSRAG.WebControls.Calendar;
using Constants = PHSRAG.Insight21.InsightUtilities.Constants;
using SetFocus = PHSRAG.WebControls.SetFocus;
using AjaxControlToolkit;
#endregion

namespace PHSRAG.Insight21.InsightWebControls
{
	#region Interface ISearch
	/// <summary>
	/// The ISearch interface MUST be implemented by all extended search controls.
	/// </summary>
	public interface ISearch
	{
		/// <summary>
		/// Reset the internal search state.
		/// </summary>
		void Reset();

		/// <summary>
		/// Get current search criteria.
		/// </summary>
		SearchCriteria SearchCriteria { get; }

		
		/// <summary>
		/// Generated an up-to-date version of the search criteria. This does NOT update the cached version of the
		/// criteria.
		/// </summary>
		/// <returns>The SearchCriteria object</returns>
		SearchCriteria UpdateSearchCriteria();

	}
	#endregion

	#region Class CommonSearch
	/// <summary>
	/// The CommonSearch class provides the common part of the search control used by summary/overview pages.
	/// </summary>
	/// <remarks>
	/// <protocolId>Typically, the overall search control is made up of two parts; a static part which is displayed at all times, and
	/// a dynamic part which is displayed only when the extended search option is chosen. This dynamic expand/collapse
	/// is typically provided by the ExpandingPanel control. The CommonSearch control is typically part of the static
	/// part of the ExpandingPanel.</protocolId>
	/// <protocolId>The CommonSearch control hosts a pair of configurable data fields (TextBox), the picker controls, a search button,
	/// and clear button.</protocolId>
	/// <protocolId>The pair of configurable data fields allow specification of their prompt string and ID, allowing for the usage
	/// of the CommonSearch control across pages. In addtion, their visibility may be controlled using a property.</protocolId>
	/// <protocolId>The configurable data fields support the following properties:<br></br>
	/// <list type="table">
	///		<listheader>
	///			<term>Property</term>
	///			<description>Usage</description>
	///		</listheader>
	///		<item>
	///			<term>Field1ID/Field2ID</term>
	///			<description>The ID for the field1, and is the key in the search criteria for field1 value</description>
	///		</item>
	///		<item>
	///			<term>Field1Prompt/Field2Prompt</term>
	///			<description>This is the prompt string displayed to the left of the field</description>
	///		</item>
	///		<item>
	///			<term>Field1Visible/Field2Visible</term>
	///			<description>When set to false, hides the field</description>
	///		</item>
	/// </list>
	/// </protocolId>
	/// <protocolId>In most cases, the CommonSearch control is used in concert with another control which is specific to a page or
	/// a module. Such a control is referred to as an ExtendedSearch control.</protocolId>
	/// <protocolId>When an ExtendedSearch is used, it is typically contained with the dymanic part of the ExpandingPanel.
	/// The CommonSearch control needs to know of all such ExtendedSearch controls so that it may interact with them
	/// during the steps of search criteria collection, resetting of state, etc. This relationship is specified using
	/// the <b>ExtendedSearchControl</b> property of the CommonSearch control. The value of this property is the ID of
	/// the ExtendedSearch control. If more than one ExtendedSearch control is used, then the value is a comma-separted
	/// list of their IDs.</protocolId>
	/// <protocolId>When the Search button is clicked upon, the CommonSearch control can inform the containing page of that action.
	/// This is achieved by the page using the <b>OnSearch</b> event of the CommonSearch control. The delegate to handle
	/// that event has the following signature:</protocolId>
	/// <protocolId>void OnSearch(SearchCriteriaArgs args)</protocolId>
	/// <protocolId>The SearchCriteriaArgs class extends the .NET EventArgs class, and provides properties to access the current
	/// search criteria and its version.</protocolId>
	/// </remarks>
	/// <example>
	/// <protocolId>Here is an example of how the CommonSearch and an extended seach controls is used on a page:</protocolId>
	/// <code>
	/// <![CDATA[
	/// <%@ Register TagPrefix="cc1" Namespace="PHSRAG.Insight21.InsightWebControls" Assembly="PHSRAG.Insight21.InsightWebControls" %>
	/// <%@ Register TagPrefix="cc2" Namespace="PHSRAG.WebControls" Assembly="PHSRAG.WebControls" %>
	/// ...
	/// <body>
	/// ...
	/// <cc2:ExpandingPanel id="expandingPanel" runat="server" ExpansionToggler="expansionToggler">
	///		<StaticTemplate>
	///			<table class="Search">
	///				<tr>
	/// 				<td>
	///						<cc1:CommonSearch id="commonSearch" runat="server" Module="Agreements"
	///							ExtendedSearchControl="agreementSearch" OnSearch="OnSearch" OnClearSelection="OnClearSelection"
	///							Field1Visible="true" />
	///					</td>
	///				</tr>
	///				<tr>
	///					<td><cc2:ExpansionToggler id="expansionToggler" runat="Server"
	///						CollapsedLinkCaption="Show Advanced Search Options"
	///						ExpandedLinkCaption="Hide Advanced Search Options"
	///						CollapsedImage="~/Images/Arrow_RT.gif"
	///						ExpandedImage="~/Images/Arrow_DN.gif" />
	///					</td>
	///			</tr>
	///			</table>
	///		</StaticTemplate>
	///		<DynamicTemplate>
	///			<table class="Search">
	///				<tr>
	///					<td>
	///						<cc1:AgreementSearch id="agreementSearch" runat="server"
	/// 						  HasFundStatusDropDownList="True" IsSearchableByBalance="False"
	/// 						  HasIncludeProgramProjectsCheckBox="True" />
	/// 				</td>
	/// 			</tr>
	///			</table>
	///		</DynamicTemplate>
	/// </cc2:ExpandingPanel>
	/// ...
	/// </body>
	/// ]]>
	/// </code>
	/// </example>
	/// <example>
	/// <protocolId>Here is an example of how the OnSearch event handler is used:</protocolId>
	/// <protocolId>The SearchCriteria class provides a version number which may be used by pages to determine whether or not
	/// they should reload their contents. Every time the search criteria changes, the CommonSearch control increments
	/// the version number. When the search criteria is reset, the version number reverts to 0.</protocolId>
	/// <code>
	/// <![CDATA[
	///	protected void OnSearch(SearchCriteriaArgs args)
	///	{
	///		SearchCriteria searchCriteria = args.SearchCriteria;
	///		if (searchCriteria != null)
	///		{
	///			StringBuilder sb = new StringBuilder();
	///			sb.AppendFormat("<b>Search Criteria (Version {0}):</b><br>", searchCriteria.Version);
	///
	///			if (searchCriteria.Count > 0)
	///				foreach (string key in searchCriteria.Keys)
	///					sb.AppendFormat("{0} : {1}<br>", key, searchCriteria[key]);
	///			else
	///				sb.AppendFormat("Search criteria is empty.<br>");
	///
	///			// Now do something with the search criteia...
	///			System.Diagnostics.Trace.WriteLine(sb.ToString());
	///		}
	///	}
	/// ]]>
	/// </code>
	/// </example>
	[ToolboxData("<{0}:CommonSearch runat=server />"), Designer(typeof(CommonSearchDesigner))]
	public class CommonSearch : BaseSearch, INamingContainer, ISearch
	{
		#region Public Constants
		/// <summary>Separator character between a search criteria label and its value</summary>
		public const char LabelValueSeparator = '|';
		#endregion

		#region Private Constants
		private const string ClearSelectionCommand = "ClearSelection";
		private const string ClientsScriptKey = "clientScripts_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string ClickButtonScriptKey = "clickButtonScript_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string COSPickerVisibilityKey = "COSPickerVisibility_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string PIToSearchCriteriaKey = "PIToSearchCriteria_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string EmbeddedExtendedSearchControlKey = "embeddedExtendedSearchControl_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string EmbeddedExtendedSearchControlPropertiesKey = "embeddedExtendedSearchControlProperties_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string ExtendedSearchControlKey = "extendedSearchControl_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string Field1IDKey = "field1ID_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string Field1PromptKey = "field1Prompt_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string Field1VisibilityKey = "field1Visibility_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string Field1EnabledKey = "field1Enabled_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string Field1StyleKey = "field1Style_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string Field1AutoCompleteServicePathKey = "Field1AutoCompleteServicePath_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string Field1AutoCompleteServiceMethodKey = "Field1AutoCompleteServiceMethod_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string Field2IDKey = "field2ID_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string Field2PromptKey = "field2Prompt_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string Field2VisibilityKey = "field2Visibility_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string Field2EnabledKey = "field2Enabled_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string Field2StyleKey = "field2Style_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string Field2AutoCompleteServicePathKey = "Field2AutoCompleteServicePath_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string Field2AutoCompleteServiceMethodKey = "Field2AutoCompleteServiceMethod_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string HideAllCommonControlsKey = "HideAllCommonControls_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string LabPickerVisibilityKey = "LabPickerVisibility_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string ModuleKey = "Module_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string PIPickerVisibilityKey = "PIPickerVisibility_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
        private const string SearchCommand = "Search";
		private const string PickerTypeLab = "Lab";
		private const string PickerTypePI = "PI";
		private const string NoPIAccessDisplayMessage = "No PI Access";
		private const string ViewOptionVisibilityKey = "viewOptionVisibility_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		#endregion

		#region Instance Variables
		private Policy.ICachePolicy	cachePolicy;
		private ImageButton			clearSelection;
		private ControlCollection	controlCollection;
		private ImageButton			cos;
		private string				cosPickerControl = "../Common/SearchSupport/COSPicker.ascx";
		private WebControl			embeddedSearchControl;
		private TextBox				field1;
		private Label				field1Label;
		private TextBox				field2;
		private Label				field2Label;
        private AutoCompleteExtender autoCompleteEx1;
        private AutoCompleteExtender autoCompleteEx2;
        private ToolkitScriptManager scriptManager;
		private ImageButton			lab;
		private string				labPickerFilterColumn = "LabName";
		private string				labPickerLoader = "GetLabList";
		private ImageButton			pi;
		private string				piPickerFilterColumn = "PIName";
		private string				piPickerLoader = "GetPIList";
		private string				pickerControl = "../Common/SearchSupport/Picker.ascx";
		private string				pickerHostPage = "../Common/ControlHost.aspx";
        private SetFocus            setFocus;
		private ImageButton			search;
		private Label				selectedCOS;
		private Label				selectedLab;
		private Label				selectedPI;
		private DropDownList		viewOption;
		#endregion

		#region Public Events
		/// <summary>
		/// Add/Remove an event handler for the Search event.
		/// </summary>
		[Category("Action"), Description("Raised when the clean selection button is clicked")]
		public event CommandEventHandler ClearSelection
		{
			add		{ Events.AddHandler(ClearSelectionCommand, value); }
			remove	{ Events.RemoveHandler(ClearSelectionCommand, value); }
		}

		/// <summary>
		/// Add/Remove an event handler for the Search event.
		/// </summary>
		[Category("Action"), Description("Raised when the search button is clicked")]
		public event SearchEventHandler Search
		{
			add		{ Events.AddHandler(SearchCommand, value); }
			remove	{ Events.RemoveHandler(SearchCommand, value); }
		}
		#endregion

		#region Public Properties
		/// <summary>
		/// Get/Set the visibility of the COS picker.
		/// </summary>
		public bool COSPickerVisible
		{
			get { object o = ViewState[COSPickerVisibilityKey]; return (o == null) ? true : (bool)o; }
			set { ViewState[COSPickerVisibilityKey] = value; }
		}

		/// <summary>
		/// Get/Set if PI is added to searchCriteria, default is true 
		/// </summary>
		public bool AddSinglePIToSearchCriteria
		{
			get { object o = ViewState[PIToSearchCriteriaKey]; return (o == null) ? true : (bool)o; }
			set { ViewState[PIToSearchCriteriaKey] = value; }
		}

		/// <summary>
		/// Get/Set the extended search control to embed.
		/// </summary>
		public string EmbeddedExtendedSearchControl
		{
			get { return ViewState[EmbeddedExtendedSearchControlKey] as string; }
			set { ViewState[EmbeddedExtendedSearchControlKey] = value; }
		}

		/// <summary>
		/// Get/Set the properties of the embedded extended search control.
		/// </summary>
		public string EmbeddedExtendedSearchControlProperties
		{
			get { return ViewState[EmbeddedExtendedSearchControlPropertiesKey] as string; }
			set { ViewState[EmbeddedExtendedSearchControlPropertiesKey] = value; }
		}

		/// <summary>
		/// Get/Set the control (if any) which provides extended search capabilities.
		/// </summary>
		public string ExtendedSearchControl
		{
			get { return ViewState[ExtendedSearchControlKey] as string; }
			set { ViewState[ExtendedSearchControlKey] = value; }
		}

		/// <summary>
		/// Get the control (if any) which provides extended search capabilities.
		/// </summary>
		public Control EmbeddedSearchControl
		{
			get { return (embeddedSearchControl != null) ? embeddedSearchControl : null as Control; }
		}

		/// <summary>
		/// Get/Set the ID of the control which represents the first field.
		/// </summary>
		public string Field1ID
		{
			get { string s = ViewState[Field1IDKey] as string; return (s == null) ? "field1ID" : s; }
			set { ViewState[Field1IDKey] = value; }
		}

		/// <summary>
		/// Get/Set the name of the control which represents the first field.
		/// </summary>
		public string Field1Prompt
		{
			get { string s = ViewState[Field1PromptKey] as string; return (s == null) ? "Field1 Prompt:" : s; }
			set { ViewState[Field1PromptKey] = value; }
		}

		/// <summary>
		/// Get/Set the style of the first field.
		/// </summary>
		public string Field1Style
		{
			get { string s = ViewState[Field1StyleKey] as string; return (s == null) ? "DataInput" : s; }
			set { ViewState[Field1StyleKey] = value; }
		}

		/// <summary>
		/// Get/Set the visibility of the first field.
		/// </summary>
		public bool Field1Visible
		{
			get { object o = ViewState[Field1VisibilityKey]; return (o == null) ? true : (bool)o; }
			set { ViewState[Field1VisibilityKey] = value; }
		}
        /// <summary>
        /// Get/Set the enabled status of the first field.
        /// </summary>
        public bool Field1Enabled
        {
            get { object o = ViewState[Field1EnabledKey]; return (o == null) ? true : (bool)o; }
            set { ViewState[Field1EnabledKey] = value; }
        }

        /// <summary>
        /// Get/Set the path of the webservice to get the auto complete values from
        /// </summary>
        public string Field1AutoCompleteServicePath
        {
            get { string s = ViewState[Field1AutoCompleteServicePathKey] as string; return (s == null) ? "" : s; }
            set { ViewState[Field1AutoCompleteServicePathKey] = value; }
        }

        /// <summary>
        /// Get/Set the method to get the auto complete values from
        /// </summary>
        public string Field1AutoCompleteServiceMethod
        {
            get { string s = ViewState[Field1AutoCompleteServiceMethodKey] as string; return (s == null) ? "" : s; }
            set { ViewState[Field1AutoCompleteServiceMethodKey] = value; }
        }

        /// <summary>
		/// Get/Set the ID of the control which represents the second field.
		/// </summary>
		public string Field2ID
		{
			get { string s = ViewState[Field2IDKey] as string; return (s == null) ? "field2ID" : s; }
			set { ViewState[Field2IDKey] = value; }
		}

		/// <summary>
		/// Get/Set the name of the control which represents the second field.
		/// </summary>
		public string Field2Prompt
		{
			get { string s = ViewState[Field2PromptKey] as string; return (s == null) ? "Field2 Prompt:" : s; }
			set { ViewState[Field2PromptKey] = value; }
		}

		/// <summary>
		/// Get/Set the style of the second field.
		/// </summary>
		public string Field2Style
		{
			get { string s = ViewState[Field2StyleKey] as string; return (s == null) ? "DataInput" : s; }
			set { ViewState[Field2StyleKey] = value; }
		}

		/// <summary>
		/// Get/Set the visibility of the second field.
		/// </summary>
		public bool Field2Visible
		{
			get { object o = ViewState[Field2VisibilityKey]; return (o == null) ? true : (bool)o; }
			set { ViewState[Field2VisibilityKey] = value; }
		}
        /// <summary>
        /// Get/Set the enabled status of the second field.
        /// </summary>
        public bool Field2Enabled
        {
            get { object o = ViewState[Field2EnabledKey]; return (o == null) ? true : (bool)o; }
            set { ViewState[Field2EnabledKey] = value; }
        }

        /// <summary>
        /// Get/Set the path of the webservice to get the auto complete values from
        /// </summary>
        public string Field2AutoCompleteServicePath
        {
            get { string s = ViewState[Field2AutoCompleteServicePathKey] as string; return (s == null) ? "" : s; }
            set { ViewState[Field2AutoCompleteServicePathKey] = value; }
        }

        /// <summary>
        /// Get/Set the method to get the auto complete values from
        /// </summary>
        public string Field2AutoCompleteServiceMethod
        {
            get { string s = ViewState[Field2AutoCompleteServiceMethodKey] as string; return (s == null) ? "" : s; }
            set { ViewState[Field2AutoCompleteServiceMethodKey] = value; }
        }
        
        /// <summary>
		/// Get/Set the visibility of all common controls (except the search and clear buttons).
		/// </summary>
		public bool HideAllCommonControls
		{
			get { object o = ViewState[HideAllCommonControlsKey]; return (o == null) ? false : (bool)o; }
			set { ViewState[HideAllCommonControlsKey] = value; }
		}

		/// <summary>
		/// Set the name of the stored procedrue used to load the lab data.
		/// </summary>
		public string LabPickerLoader
		{
			set { labPickerLoader = value; }
		}

		/// <summary>
		/// Get/Set the visibility of the Lab picker.
		/// </summary>
		public bool LabPickerVisible
		{
			get { object o = ViewState[LabPickerVisibilityKey]; return (o == null) ? true : (bool)o; }
			set { ViewState[LabPickerVisibilityKey] = value; }
		}

		/// <summary>
		/// Get/Set the name of the module within which this control is operating.
		/// </summary>
		public string Module
		{
			get
			{
				object o = ViewState[ModuleKey];
				if (o == null)
					throw new Exception("Common search requires setting of the Module name by the page");
				return (string)o;
			}
			set { ViewState[ModuleKey] = value; }
		}

		/// <summary>
		/// Set the URL to the page which will host the PI/Lab/COS picker controls.
		/// </summary>
		public string PickerHostPage
		{
			set { pickerHostPage = value; }
		}

		/// <summary>
		/// Set the path to the control which provides the PI picker functionality.
		/// </summary>
		public string PickerControl
		{
			set { pickerControl = value; }
		}

		/// <summary>
		/// Set the name of the stored procedrue used to load the PI data.
		/// </summary>
		public string PIPickerLoader
		{
			set { piPickerLoader = value; }
		}

		/// <summary>
		/// Get/Set the visibility of the PI picker.
		/// </summary>
		public bool PIPickerVisible
		{
			get { object o = ViewState[PIPickerVisibilityKey]; return (o == null) ? true : (bool)o; }
			set { ViewState[PIPickerVisibilityKey] = value; }
		}

        /// <summary>
		/// Get the string used to indicate within a control that a value has to be selected.
		/// </summary>
		public static string SelectPrompt
		{
			get { return Helper.SelectPrompt; }
		}

		/// <summary>
		/// Get/Set the visibility of the View Option drop-down-list.
		/// </summary>
		public bool ViewOptionVisible
		{
			get { object o = ViewState[ViewOptionVisibilityKey]; return (o == null) ? true : (bool)o; }
			set { ViewState[ViewOptionVisibilityKey] = value; }
		}
		#endregion

		#region ISearch Methods/Properties
		/// <summary>
		/// Reset the internal search state.
		/// </summary>
		public void Reset()
		{
			ResetControls(controlCollection);
			((CommonSearchContext)cachePolicy.GetCachedObject(CommonSearchContext.CacheKey)).Reset(Page.Request.Url.ToString());

			selectedLab.Text = selectedPI.Text = selectedCOS.Text = string.Empty;

			foreach (ISearch extendedSearchControl in GetExtendedSearchControls())
				extendedSearchControl.Reset();

			if (embeddedSearchControl != null)
				((ISearch)embeddedSearchControl).Reset();
		}

		
		/// <summary>
		/// Get search criteria for the CommonSearch control. This includes the PI/Lab/COS picker information.
		/// </summary>
		public SearchCriteria SearchCriteria
		{
			get
			{
				return ((CommonSearchContext)cachePolicy.GetCachedObject(CommonSearchContext.CacheKey)).GetSearchCriteria(Page.Request.Url.ToString());
			}
		}

	
		/// <summary>
		/// Get the composite of the common search criterial and the search criteria from all extended search controls.
		/// </summary>
		/// <returns>The composite SearchCriteria</returns>
		/// <remarks>
		/// Every invocation to this method forces an update of the search criteria and its caching.
		/// </remarks>
		public SearchCriteria UpdateSearchCriteria()
		{
			UserContext userContext = (UserContext)cachePolicy.GetCachedObject(UserContext.CacheKey);
			CommonSearchContext searchContext = (CommonSearchContext)cachePolicy.GetCachedObject(CommonSearchContext.CacheKey);

			// Gather the common search criteria first.
			
			SearchCriteria searchCriteria = GetSearchCriteria(userContext.DomainUserId, controlCollection);

			// If there are extended seach controls, get the search criteria from each of them and add them to the
			// search criteria.

			ArrayList extendedSearchControls = GetExtendedSearchControls();
			foreach (ISearch extendedSearchControl in extendedSearchControls)
				MergeSearchCriteria(searchCriteria, extendedSearchControl.UpdateSearchCriteria());

			if (embeddedSearchControl != null)
				MergeSearchCriteria(searchCriteria, ((ISearch)embeddedSearchControl).UpdateSearchCriteria());

			// The PI/Lab/COS pickers directly add their selection to the search criteria collection maintained by
			// the common search context. We have to add them to the search criteria currently being built.

			SearchCriteria oldSearchCriteria = SearchCriteria;
			if (oldSearchCriteria[CommonSearchContext.COSPickerKey] != null)
			{
				searchCriteria.ControlLabels[CommonSearchContext.COSPickerKey] =
					String.Format("{0}{1}{2}", selectedCOS.Attributes["Label"], LabelValueSeparator,
					CommonSearchContext.GetPickerDescription(CommonSearchContext.COSPickerKey, oldSearchCriteria[CommonSearchContext.COSPickerKey]));
				searchCriteria[CommonSearchContext.COSPickerKey] = oldSearchCriteria[CommonSearchContext.COSPickerKey];
				searchCriteria.Dirty = true;
			}

			if (oldSearchCriteria[CommonSearchContext.LabPickerKey] != null)
			{
				searchCriteria.ControlLabels[CommonSearchContext.LabPickerKey] =
					String.Format("{0}{1}{2}", selectedLab.Attributes["Label"], LabelValueSeparator,
					CommonSearchContext.GetPickerDescription(CommonSearchContext.LabPickerKey, oldSearchCriteria[CommonSearchContext.LabPickerKey]));
				searchCriteria[CommonSearchContext.LabPickerKey] = oldSearchCriteria[CommonSearchContext.LabPickerKey];
				searchCriteria.Dirty = true;
			}

			if (oldSearchCriteria[CommonSearchContext.PIPickerKey] != null)
			{
				searchCriteria.ControlLabels[CommonSearchContext.PIPickerKey] =
					String.Format("{0}{1}{2}", selectedPI.Attributes["Label"], LabelValueSeparator,
					CommonSearchContext.GetPickerDescription(CommonSearchContext.PIPickerKey, oldSearchCriteria[CommonSearchContext.PIPickerKey]));
				searchCriteria[CommonSearchContext.PIPickerKey] = oldSearchCriteria[CommonSearchContext.PIPickerKey];
				searchCriteria.Dirty = true;
			}

			// It is crucial that if the search criteria has changed, we store it back into the search context so that
			// its retrieval at a later point of time will return correct values. To indicate that there has been a
			// change, we will increment the version number.
			
			if (!oldSearchCriteria.Equals(searchCriteria))
			{
				searchCriteria.Version = oldSearchCriteria.Version + 1;
				//MergeSearchCriteria(searchCriteria, oldSearchCriteria);
				searchContext.SetSearchCriteria(Page.Request.Url.ToString(), searchCriteria, oldSearchCriteria);
			}
			
			return searchCriteria;
		}

		
		/// <summary>
		/// Merge an extended search criteria (provided by either an extended or an embedded search control) with the criteria
		/// maintained by this control. The merged criteria ultimately becomes the single entity.
		/// </summary>
		/// <param name="searchCriteria">SearchCriteria into which the merge takes place</param>
		/// <param name="extendedSearchCriteria">SearchCriteria which will be merged</param>
		private void MergeSearchCriteria(SearchCriteria searchCriteria, SearchCriteria extendedSearchCriteria)
		{
			if (extendedSearchCriteria != null)
			{
				foreach (string key in extendedSearchCriteria.Keys)
					if ( FindControl(key) == null)
						searchCriteria[key] = extendedSearchCriteria[key];
				
				foreach (string key in extendedSearchCriteria.ControlLabels.Keys)
					if ( FindControl(key) == null)
						searchCriteria.ControlLabels[key] = extendedSearchCriteria.ControlLabels[key];
			}
		}
		#endregion

		#region Protected Overridable Methods
		/// <summary>
		/// This is a hook for derived controls to influence creation of additional controls.
		/// </summary>
		protected virtual void CreateAdditionalControls()
		{
		}

		/// <summary>
		/// This is a hook for derived controls to influence generation of tree for additional controls.
		/// </summary>
		protected virtual void GenerateAdditionalControlTree()
		{
		}
		#endregion

		#region Private Properties
		/// <summary>
		/// Determine if the control is in design mode or not.
		/// </summary>
		private static bool IsDesignMode
		{
			get { return HttpContext.Current == null; }
		}
		
		/// <summary>
		/// Gets the number of PIs the logged in user has access to.
		/// NB: [6418] Returns 0 now instead of -1 so it does not display.
		/// </summary>
		private static int LabCount 
		{
			get 
			{
//				return ((CommonSearchContext)cachePolicy.GetCachedObject(CommonSearchContext.CacheKey)).
//					GetPickerData(PickerTypeLab, labPickerLoader).Rows.Count;
				return 0;
			}
		}

		/// <summary>
		/// Gets the number of PIs the logged in user has access to.
		/// </summary>
		private static int PICount 
		{
			get 
			{
//				return ((CommonSearchContext)cachePolicy.GetCachedObject(CommonSearchContext.CacheKey)).
//					GetPickerData(PickerTypePI, piPickerLoader).Rows.Count;
				return -1;
			}
		}
		#endregion

		#region Overrides
		/// <summary>
		/// Invoked when an event is raised by a child control.
		/// </summary>
		/// <param name="source">Reference to the child control that raised the event</param>
		/// <param name="e">Argument associated with the event</param>
		/// <returns>True if the event was handle by this method; false otherwise</returns>
		protected override bool OnBubbleEvent(object source, EventArgs e)
		{
			// Only those events raised by link button controls are currently handled. Further, only the "search" link button 
			// command is handled.  On the search event, gather the search criteria from all controls (including all extended criteria
			// controls). If the criteria has changed since it was cached last, the cache will be updated to reflect the
			// new crieria.
			// If the invoking entity has registred a handler for the search event, the handler will be invoked providing
			// it with the current search criteria.

			bool handled = false;
			if (source is ImageButton)
			{
				string commandName = ((CommandEventArgs)e).CommandName;
				if (commandName == SearchCommand)
				{
					UpdateSearchCriteria();
					SearchEventHandler handler = (SearchEventHandler)Events[commandName];
					if (handler == null)
						throw new Exception(string.Format("CommonSearch control requires the {0} event to be handled", SearchCommand));
					handler(new SearchCriteriaArgs(commandName, SearchCriteria));
					handled = true;
				}
			}
			return handled;
		}

		/// <summary>
		/// Create all controls. Notice that the creation is more than just instantiating the controls. It also involves
		/// in generating the underlying HTML so that the controls shows up per the need.
		/// </summary>
		protected override void CreateChildControls()
		{
			CreateControls();
			GenerateControlTree();
		}

		/// <summary>
		/// Initializes the cachepolicy. Also calls a method that sets the PI search criteria 
		/// for users with access to only one PI. It is necessary for this call to be 
		/// made here because the PIID needs to be in the search criteria before the page is loaded with data.
		/// Create and cache a CommonSearchContext object if necessary.
		/// Note that controls whose state must persist in the view state MUST be created BEFORE OnInit completes. We do
		/// this simply by invoking EnsureChildControls().
		/// </summary>
		/// <param name="e">Argument associated with the event</param>
		protected override void OnInit(EventArgs e)
		{
			cachePolicy = (Policy.ICachePolicy)Page.Session[Policy.Names.CachePolicyKey];
			CommonSearchContext searchContext = cachePolicy.GetCachedObject(CommonSearchContext.CacheKey) as CommonSearchContext;
			if (searchContext == null)
			{
                searchContext = new CommonSearchContext(cachePolicy);
				searchContext.Cache();
			}

			EnsureChildControls();
			base.OnInit(e);
			searchContext.GetSearchCriteria(Page.Request.Url.ToString(), controlCollection);
		}

		/// <summary>
		/// On non-postback, load the view-option drop-down-list and store the search button's client id.
		/// </summary>
		/// <param name="e">Argument associated with the event</param>
		protected override void OnLoad(EventArgs e)
		{
            string searchClientId = cachePolicy.GetCachedObject(Constants.SearchButtonClientIDKey) as string;
            if (searchClientId != search.ClientID)
                cachePolicy.Cache(Constants.SearchButtonClientIDKey, search.ClientID);

			if (!Page.IsPostBack && (EmbeddedExtendedSearchControl == null))
				LoadViewOption();
		}

		/// <summary>
		/// Set the state for all constituent controls, and register a client script block for the express purpose of
		/// providing a communication mechanism to the picker control to pass context.
		/// </summary>
		/// <param name="e">Argument associated with the event</param>
		/// <remarks>
		/// <protocolId>Why are the actions taken in this method not being done on control load?</protocolId>
		/// <protocolId>During the non-postback construction of the control, the OnLoad method is invoked BEFORE the
		/// CreateChildControls method. The actual creation of constitutent controls are done within the context of
		/// the CreateChildControls methos. Setting the state of control relies on (obviously) the fact that the controls
		/// have been created. So, setting the state in OnLoad for the non-postback condition would fail.</protocolId>
		/// <protocolId>Interestingly, the sequence of method invocations reverses on postback: CreateChildControls is invoked
		/// before OnLoad.</protocolId>
		/// </remarks>
		protected override void OnPreRender(EventArgs e)
		{
			if (!IsDesignMode)
			{
                // Set these here so that we can late bind the properties
                if (autoCompleteEx1 != null)
                {
                    autoCompleteEx1.ServicePath = Field1AutoCompleteServicePath;
                    autoCompleteEx1.ServiceMethod = Field1AutoCompleteServiceMethod;
                }

                if (autoCompleteEx2 != null)
                {
                    autoCompleteEx2.ServicePath = Field2AutoCompleteServicePath;
                    autoCompleteEx2.ServiceMethod = Field2AutoCompleteServiceMethod;
                }

				CommonSearchContext searchContext = cachePolicy.GetCachedObject(CommonSearchContext.CacheKey) as CommonSearchContext;
				if (searchContext != null)
					SetControlState(searchContext.GetSearchCriteria(Page.Request.Url.ToString(), controlCollection));

				// The client script (below) is invoked by the picker page just before it closes its window. This allows
				// us to display the picker selection within the context of this search control without doing any postback.
				// There is more to it than that.
				// Imagine a scenario where the user had entered something into the text box controls and then clicks on
				// one of the pickers. If at the end of the picker selection, a reload of the window within which this
				// control is instigated, then whatever data was entered in the text boxes are lost since there has been
				// no postback. A reload is NOT a postback. The client script fits the bill nicely.
				// It is CRUCIAL that the script registration be NOT done post pre-render. The pre-render is the last chance
				// we have to do such things.

				if (!Page.ClientScript.IsClientScriptBlockRegistered(ClientsScriptKey))
				{
					const string dummyID = "DummyControlID";
					string script = string.Format(
						"<script language=\"javascript\">\r\n" +
						"var browserIsIE = (navigator.appName.indexOf('Microsoft') != -1);\r\n\r\n" +
						"function SetPickerSelection(pickerType, pickerValue)\r\n" +
						"{{\r\n" +
						"\tvar pickerControl = null;\r\n" +
						"\tswitch (pickerType)\r\n" +
						"\t{{\r\n" +
						"\t\tcase \"{0}\":\r\n" +
						"\t\t\tpickerControl = document.getElementById(\"{1}\");\r\n" +
						"\t\t\tbreak;\r\n" +
						"\t\tcase \"{2}\":\r\n" +
						"\t\t\tpickerControl = document.getElementById(\"{3}\");\r\n" +
						"\t\t\tbreak;\r\n" +
						"\t\tcase \"{4}\":\r\n" +
						"\t\t\tpickerControl = document.getElementById(\"{5}\");\r\n" +
						"\t\t\tbreak;\r\n" +
						"\t}}\r\n" +
						"\tif (pickerControl != null)\r\n" +
						"\t\tpickerControl.innerText = \"Selected \" + pickerType + \": \" + pickerValue;\r\n" +
						"}}\r\n" +
						"</script>\r\n",
						CommonSearchContext.PIPickerKey, selectedPI == null ? dummyID : selectedPI.ClientID,
						CommonSearchContext.LabPickerKey, selectedLab == null ? dummyID : selectedLab.ClientID,
						CommonSearchContext.COSPickerKey, selectedCOS == null ? dummyID : selectedCOS.ClientID);

					Page.ClientScript.RegisterClientScriptBlock(GetType( ), ClientsScriptKey, script);
				}

                if (!Page.ClientScript.IsClientScriptBlockRegistered(ClickButtonScriptKey))
                {
                    string script2 = string.Format(
                        "<script language=\"javascript\">\r\n" +
                        "function ClickButton(e, buttonid){{\r\n" +
                        "\tvar evt = e ? e : window.event;\r\n" +
                        "\tvar bt = document.getElementById(buttonid);\r\n" +
                        "\tif (typeof bt == 'object'){{\r\n" +
                        "\t\tif (evt.keyCode == 13){{\r\n" +
                        "\t\t\tbt.click();\r\n" +
                        "\t\t\treturn false;\r\n" +
                        "\t\t}}\r\n" +
                        "\t}}\r\n" +
                        "}}\r\n" +
                        "</script>\r\n");

                    Page.ClientScript.RegisterClientScriptBlock(GetType( ), ClickButtonScriptKey, script2);
                }

				const string CommonSearchScriptKey = "ManagedScript";
				if (!Page.ClientScript.IsClientScriptBlockRegistered(CommonSearchScriptKey))
				{
					if (field1 != null)
					{
						string script = string.Format(
							"<script language=\"javascript\">\r\n" +
							"function OnPONumberChanged()\r\n" +
							"{{\r\n" +
							"\ttry\r\n" +
							"\t{{\r\n" +
							"\t\t if (ManagePONumber != null)\r\n" +
							"\t ManagePONumber('" + field1.ClientID + "');\r\n" +
							"\t}}\r\n" +
							"\tcatch(x)\r\n" +
							"\t{{\r\n" +
							"\t\t \r\n" +
							"\t}}\r\n" +
							"}}\r\n" +	
							"function OnVoucherNumberChanged()\r\n" +
							"{{\r\n" +
							"\ttry\r\n" +
							"\t{{\r\n" +
							"\t\t if (ManageVoucherNumber != null)\r\n" +
							"\t ManageVoucherNumber('" + field1.ClientID + "');\r\n" +
							"\t}}\r\n" +
							"\tcatch(x)\r\n" +
							"\t{{\r\n" +
							"\t\t \r\n" +
							"\t}}\r\n" +
							"}}\r\n" +	
							"function OnOrderTypeChanged()\r\n" +
							"{{\r\n" +
							"\ttry\r\n" +
							"\t{{\r\n" +
							"\t\t if (ManageOrderType != null)\r\n" +
							"\t ManageOrderType('" + field1.ClientID + "');\r\n" +
							"\t}}\r\n" +
							"\tcatch(x)\r\n" +
							"\t{{\r\n" +
							"\t\t \r\n" +
							"\t}}\r\n" +
							"}}\r\n" +
							"</script>\r\n");

						Page.ClientScript.RegisterClientScriptBlock(GetType( ), CommonSearchScriptKey, script);
					}
				}				
			}

			base.OnPreRender(e);
		}

		/// <summary>
		/// Send control content to the HtmlTextWriter object, which writes the content to be rendered on the client.
		/// When in design mode, instigate creation of all controls.
		/// </summary>
		/// <param name="writer">The writer to use for rendering</param>
		protected override void Render(HtmlTextWriter writer)
		{
			if (IsDesignMode)
				EnsureChildControls();

			base.Render(writer);
		}
		#endregion

		#region Event Handlers
		/// <summary>
		/// Clear all search control contents, and purge any cached search criteria.
		/// </summary>
		/// <param name="sender">Control which raised the event</param>
		/// <param name="e">Arguments associated with the event</param>
		private void OnClearSelection(object sender, ImageClickEventArgs e)
		{
			Reset();

			CommandEventHandler handler = (CommandEventHandler)Events[ClearSelectionCommand];
			if (handler != null)
				handler(this, new CommandEventArgs(ClearSelectionCommand, this));
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Create all controls in the CommonSearch. If an embedded search control is specified, it will predominate over any
		/// all other controls excepting the search and clear link buttons and the set focus control.
		/// </summary>
		private void CreateControls()
		{
			Controls.Clear();
			controlCollection = new ControlCollection(this);

			clearSelection = new ImageButton();
			clearSelection.CssClass = "ClearSelectionImage";
			clearSelection.ID = clearSelection.CommandName = "clearSelection";
			clearSelection.ImageUrl = string.Format("{0}/Images/b_clearselect_grey.gif", IsDesignMode ? ".." : "~");
			clearSelection.Click += new ImageClickEventHandler(OnClearSelection);

			search = new ImageButton();
			search.ImageUrl = string.Format("{0}/Images/b_search_grey.gif", IsDesignMode ? ".." : "~");
			search.ID = "search";
			search.CommandName = SearchCommand;

            setFocus = new SetFocus();
            setFocus.ID = "setFocus";
            setFocus.FocusControl = "search";
            
            CreateCommonControls();
			string embeddedControl = EmbeddedExtendedSearchControl;
			if (embeddedControl != null)
				CreateEmbeddedControl(embeddedControl);
		}

		/// <summary>
		/// Create all controls which are deemed captive to this page. This is in contrast to the embedded search control which
		/// the containing entity may specify in place of all the captive controls.
		/// There are three controls that do NOT contribute to the search criteria. These controls are used simply to
		/// display the current selection made in one or more pickers. So as to avoid processing them when the current
		/// search criteria is built (see UpdateSearchCriteria()), these three controls are given a custom attribute
		/// named "ExcludeFromSearchCriteria". Any control with that attribute will be exluded when the search criteria
		/// is built.
		/// </summary>
		private void CreateCommonControls()
		{
			if (IsDesignMode || !HideAllCommonControls)
			{
				field1Label = new Label();
				field1Label.ID = "field1";
				field1Label.Text = Field1Prompt;
				field1Label.Attributes["ExcludeFromSearchCriteria"] = "true";

                field1 = new TextBox();
                field1.ID = Field1ID;
                field1.CssClass = Field1Style;
                field1Label.Visible = field1.Visible = Field1Visible;
                field1.Enabled = Field1Enabled;
                field1.Attributes["Label"] = Field1Prompt.TrimEnd(':');
                field1.Attributes["onchange"] = "OnPONumberChanged();";
                controlCollection.Add(field1);

				field2Label = new Label();
				field2Label.ID = "field2";
				field2Label.Text = Field2Prompt;
				field2Label.Attributes["ExcludeFromSearchCriteria"] = "true";

                field2 = new TextBox();
                field2.ID = Field2ID;
                field2.CssClass = Field2Style;
                field2Label.Visible = field2.Visible = Field2Visible;
                field2.Enabled = Field2Enabled;
                field2.Attributes["Label"] = Field2Prompt.TrimEnd(':');
                controlCollection.Add(field2);

                UserContext userContext = (UserContext)cachePolicy.GetCachedObject(UserContext.CacheKey);
                if (!String.IsNullOrEmpty(Field1AutoCompleteServiceMethod))
                {
                    autoCompleteEx1 = new AutoCompleteExtender();
                    autoCompleteEx1.ID = "autoComplete1";
                    autoCompleteEx1.BehaviorID = "autoCompleteEx1";
                    autoCompleteEx1.TargetControlID = Field1ID;
                    autoCompleteEx1.MinimumPrefixLength = 3;
                    autoCompleteEx1.CompletionInterval = 1000;
                    autoCompleteEx1.EnableCaching = true;
                    autoCompleteEx1.CompletionSetCount = 20;
                    autoCompleteEx1.CompletionListCssClass = "autocomplete_completionListElement";
                    autoCompleteEx1.CompletionListItemCssClass = "autocomplete_listItem";
                    autoCompleteEx1.CompletionListHighlightedItemCssClass = "autocomplete_highlightedListItem";
                    autoCompleteEx1.DelimiterCharacters = ";, :";
                    autoCompleteEx1.UseContextKey = true;
                    autoCompleteEx1.ContextKey = userContext.DomainUserId.ToString();
                    scriptManager = new ToolkitScriptManager();
                    scriptManager.ID = "scriptManager";
                    Controls.Add(scriptManager);
                    Controls.Add(autoCompleteEx1);
                }

                if (!String.IsNullOrEmpty(Field2AutoCompleteServiceMethod))
                {
                    autoCompleteEx2 = new AutoCompleteExtender();
                    autoCompleteEx2.ID = "autoComplete2";
                    autoCompleteEx2.BehaviorID = "autoCompleteEx2";
                    autoCompleteEx2.TargetControlID = Field2ID;
                    autoCompleteEx2.MinimumPrefixLength = 3;
                    autoCompleteEx2.CompletionInterval = 1000;
                    autoCompleteEx2.EnableCaching = true;
                    autoCompleteEx2.CompletionSetCount = 20;
                    autoCompleteEx2.CompletionListCssClass = "autocomplete_completionListElement";
                    autoCompleteEx2.CompletionListItemCssClass = "autocomplete_listItem";
                    autoCompleteEx2.CompletionListHighlightedItemCssClass = "autocomplete_highlightedListItem";
                    autoCompleteEx2.DelimiterCharacters = ";, :";
                    autoCompleteEx2.UseContextKey = true;
                    autoCompleteEx2.ContextKey = userContext.DomainUserId.ToString();
                    scriptManager = new ToolkitScriptManager();
                    scriptManager.ID = "scriptManager";
                    if ( !Controls.Contains(scriptManager) )
                        Controls.Add(scriptManager);
                    Controls.Add(autoCompleteEx2);
                }

                bool labPickerVisible = IsDesignMode ? true : (LabCount != 0);
				if (labPickerVisible)
				{
					lab = new ImageButton();
					lab.ID = lab.CommandName = "labPicker";
					lab.ImageUrl = string.Format("{0}/Images/Icon_Lab.gif", IsDesignMode ? ".." : "~");
				}

				bool piPickerVisible = IsDesignMode ? true : (PIPickerVisible && (PICount != 0));
				bool cosPickerVisible = IsDesignMode ? true : COSPickerVisible;

				if (cosPickerVisible)
				{
					cos = new ImageButton();
					cos.ID = cos.CommandName = "cosPicker";
					cos.ImageUrl = string.Format("{0}/Images/Icon_ORG.gif", IsDesignMode ? ".." : "~");
				}

				if (piPickerVisible)
				{
					pi = new ImageButton();
					pi.ID = pi.CommandName = "piPicker";
					pi.ImageUrl = string.Format("{0}/Images/Icon_PI.gif", IsDesignMode ? ".." : "~");
				}

				if (ViewOptionVisible)
				{
					viewOption = new DropDownList();
					viewOption.ID = Constants.ViewingOptionKey;
					viewOption.CssClass = "DataInput";
					viewOption.Attributes["Label"] = "View";
                    controlCollection.Add(viewOption);
				}

				// Wire up the pickers to display the appropriate picker control in a new window.

				string features = string.Format("width={0},height={1},scrollbars=yes,resizable=yes,status=no",
					Utility.Settings.GetKeyValue("SearchPopupWindow.Width", 750),
					Utility.Settings.GetKeyValue("SearchPopupWindow.Height", 500));

				if (pi != null)
				{
					pi.Attributes["onclick"] = string.Format("window.open('{0}?Load={1}&Loader={2}&Type=PI&FilterColumn={3}&CommonSearchMode=true', 'PIPicker', '{4}');return false;",
						pickerHostPage, pickerControl, piPickerLoader, piPickerFilterColumn, features);		
				}
			
				if (cos != null)
				{
					cos.Attributes["onclick"] = string.Format("window.open('{0}?Load={1}', 'COSPicker', '{2}');return false;",
						pickerHostPage, cosPickerControl, features);
				}

				if (lab != null)
				{
					lab.Attributes["onclick"] = string.Format("window.open('{0}?Load={1}&Loader={2}&Type=Lab&FilterColumn={3}', 'LabPicker', '{4}');return false;",
						pickerHostPage, pickerControl, labPickerLoader, labPickerFilterColumn, features);
				}

				// Invoke the overridable method to allow any derived classes to add their controls to the collection.

				CreateAdditionalControls();
			}

			selectedCOS = new Label();
			selectedCOS.ID = CommonSearchContext.COSPickerKey;
			selectedCOS.Attributes["ExcludeFromSearchCriteria"] = "true";
			selectedCOS.Attributes["PickerSelection"] = "true";
			selectedCOS.Text = string.Empty;
			selectedCOS.Attributes["Label"] = "Chief of Service";
			controlCollection.Add(selectedCOS);

			selectedLab = new Label();
			selectedLab.ID = CommonSearchContext.LabPickerKey;
			selectedLab.Attributes["ExcludeFromSearchCriteria"] = "true";
			selectedLab.Attributes["PickerSelection"] = "true";
			selectedLab.Text = string.Empty;
			selectedLab.Attributes["Label"] = "Lab";
			controlCollection.Add(selectedLab);

			selectedPI = new Label();
			selectedPI.ID = CommonSearchContext.PIPickerKey;
			selectedPI.Attributes["ExcludeFromSearchCriteria"] = "true";
			selectedPI.Attributes["PickerSelection"] = "true";
			selectedPI.Text = string.Empty;
			selectedPI.Attributes["Label"] = "Principal Investigator";
			controlCollection.Add(selectedPI);
		}

		/// <summary>
		/// Create the specified embedded control. This control will effectively preclude the creation of all but the search
		/// and clear link button from the captive control set. This allows the embedded control to appear "in-line" within the
		/// common search control in a seamless fashion.
		/// The embedded control is loaded via reflection.
		/// Note that in addition to specifying the embedded control, a list name/value pairs (comma-separated) may be specified
		/// to pass along to the control as part of its initialization.
		/// </summary>
		/// <param name="embeddedControl">Specification of the control to embed. This takes the form "namespace/class-name".</param>
		private void CreateEmbeddedControl(string embeddedControl)
		{
			if (!IsDesignMode)
			{
				string[] elements = embeddedControl.Split('/');
				string namespaceName = elements[0];
				string className = elements[1];
				embeddedSearchControl = (WebControl)PHSRAG.Utility.InstanceCreator.Create(namespaceName, className, null);
			
				string properties = EmbeddedExtendedSearchControlProperties;
				if (properties != null)
				{
					// The list of properties specified is expected to be of the form "name=value[,name=value]". To ensure that the
					// embedded control acutally does support the specified property, we retrieve all of its publicly declared
					// property (ignoring inherited ones) and build a dictionary out of it (the key is the name of the property and
					// the value is a reference to the property information). This dictionary is then used to validate each
					// specified property prior to its invocation via reflection.

					BindingFlags bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance;
					Type t = embeddedSearchControl.GetType();
					PropertyInfo[] propertyInfo = t.GetProperties(bindingFlags);
					Hashtable propertyMap = new Hashtable();
					foreach (PropertyInfo pi in propertyInfo)
						propertyMap[pi.Name] = pi;

					bindingFlags |= BindingFlags.SetProperty;
					foreach (string property in properties.Split(','))
					{
						elements = property.Split('=');
						string propertyName = elements[0];
						string propertyValue = elements[1];

						PropertyInfo pi = propertyMap[propertyName] as PropertyInfo;
						if (pi == null)
							throw new Exception(string.Format("Property '{0}' is not supported by '{1}'", propertyName, className));

						object arg = GetTypedValue(pi, propertyValue);
						if (arg != null)
							t.InvokeMember(elements[0], bindingFlags, null, embeddedSearchControl, new object[]{arg});
					}
				}
			}
		}

		/// <summary>
		/// Return a typed reference initialized with the specified value.
		/// </summary>
		/// <param name="pi">PropertyInfo associated with the type for return</param>
		/// <param name="value">Value to be set for the typed reference</param>
		/// <returns>Typed reference as an object</returns>
		private static object GetTypedValue(PropertyInfo pi, string value)
		{
			if (pi.PropertyType == typeof(bool))
				return Convert.ToBoolean(value);

			if (pi.PropertyType == typeof(string))
				return Convert.ToString(value);

			if (pi.PropertyType == typeof(int))
				return Convert.ToInt32(value);

			if (pi.PropertyType == typeof(double))
				return Convert.ToDouble(value);

			if (pi.PropertyType == typeof(DateTime))
				return Convert.ToDateTime(value);

			// More conversion can be added if necessary...

			return null;
		}

		/// <summary>
		/// Generate the control for the common controls (search and clear links) nested within a specified table row.
		/// </summary>
		/// <param name="row">Table row within which the control tree must nest</param>
		private void GenerateCommonControlTree(TableRow row)
		{
			if (IsDesignMode || !HideAllCommonControls)
			{
				TableCell cell = new TableCell();
				row.Cells.Add(cell);
				cell.Attributes["align"] = "right";
				cell.Attributes["valign"] = "middle";
				cell.Controls.Add(field1Label);
				cell.Controls.Add(new LiteralControl("&nbsp;"));

				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Attributes["align"] = "left";
				cell.Attributes["valign"] = "middle";
				if (Field1Visible)
					cell.Controls.Add(field1);
				else
					cell.Controls.Add(new LiteralControl("&nbsp;"));

				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Controls.Add(new LiteralControl("&nbsp;"));

				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Attributes["align"] = "right";
				cell.Attributes["valign"] = "middle";
				cell.Controls.Add(field2Label);
				cell.Controls.Add(new LiteralControl("&nbsp;"));
			
				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Attributes["align"] = "left";
				cell.Attributes["valign"] = "middle";
				if (Field2Visible)
					cell.Controls.Add(field2);
				else
					cell.Controls.Add(new LiteralControl("&nbsp;"));

				if (ViewOptionVisible)
				{
					cell = new TableCell();
					row.Cells.Add(cell);
					cell.Attributes["align"] = "left";
					cell.Attributes["valign"] = "middle";
					cell.Controls.Add(new LiteralControl("<nobr>View:&nbsp;</nobr>"));

					cell = new TableCell();
					row.Cells.Add(cell);
					cell.Controls.Add(viewOption);
				}

				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Controls.Add(new LiteralControl("&nbsp;"));

				if (IsDesignMode || (pi != null) || (lab != null) || (cos != null))
				{
					cell = new TableCell();
					row.Cells.Add(cell);
					cell.Attributes["align"] = "right";
					cell.Attributes["valign"] = "middle";
					cell.Controls.Add(new LiteralControl("<nobr>Limit Search By:&nbsp;</nobr>"));
				}

				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Controls.Add((pi == null) ? (Control)new LiteralControl("&nbsp;") : (Control)pi);
				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Controls.Add(new LiteralControl("&nbsp;"));

				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Controls.Add((lab == null) ? (Control)new LiteralControl("&nbsp;") : (Control)lab);
				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Controls.Add(new LiteralControl("&nbsp;"));

				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Controls.Add((cos == null) ? (Control)new LiteralControl("&nbsp;") : (Control)cos);
				cell = new TableCell();
				row.Cells.Add(cell);
				cell.Controls.Add(new LiteralControl("&nbsp;"));
			}

			// Invoke the overridable method to allow any derived classes to add their controls to the collection.

			if (IsDesignMode || !HideAllCommonControls)
				GenerateAdditionalControlTree();
		}

		/// <summary>
		/// Setup the control tree to reflect the actual (and exact) layout of the controls within the CommonSearch.
		/// </summary>
		private void GenerateControlTree()
		{
			Table table = new Table();
			table.Width = Unit.Percentage(100);
			Controls.Add(table);
			TableRow commonControlsRow = new TableRow();
			table.Rows.Add(commonControlsRow);

			GenerateCommonControlTree(commonControlsRow);
			if (EmbeddedExtendedSearchControl != null)
			{
				TableRow row = commonControlsRow;
				int cellCount = commonControlsRow.Cells.Count;
				if (cellCount > 0)
				{
					row = new TableRow();
					table.Rows.Add(row);

					TableCell cell = new TableCell();
					row.Cells.Add(cell);
					cell.ColumnSpan = cellCount;

					Table embeddedControlTable = new Table();
					embeddedControlTable.Width = Unit.Percentage(100);
					cell.Controls.Add(embeddedControlTable);

					row = new TableRow();
					embeddedControlTable.Rows.Add(row);
				}
				GenerateEmbeddedControlTree(row);
			}

			TableRow buttonRow = new TableRow();
			table.Rows.AddAt(table.Rows.Count, buttonRow);

			TableCell buttonCell = new TableCell();
			buttonRow.Cells.Add(buttonCell);
			buttonCell.ColumnSpan = commonControlsRow.Cells.Count;
			buttonCell.Attributes["align"] = "right";
			buttonCell.Controls.Add(search);
			buttonCell.Controls.Add(clearSelection);
            buttonCell.Controls.Add(setFocus);

            // Unfortunately, this must be done out of place here due to the search button's
            // client id changing when added to the control collection
            string searchClientId = cachePolicy.GetCachedObject(Constants.SearchButtonClientIDKey) as string;

            if (searchClientId != search.ClientID)
            {
                cachePolicy.Cache(Constants.SearchButtonClientIDKey, search.ClientID);
                searchClientId = search.ClientID;
            }

            table.Attributes.Add("onkeypress", "return ClickButton(event, '" + searchClientId + "')");
            
			GeneratePickerSelectionControlTree(table, commonControlsRow.Cells.Count);
		}

		/// <summary>
		/// Generate the control for the embedded search control nested within a specified table row.
		/// </summary>
		/// <param name="row">Table row within which the control tree must nest</param>
		private void GenerateEmbeddedControlTree(TableRow row)
		{
			TableCell cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(IsDesignMode ? (Control)new LiteralControl(EmbeddedExtendedSearchControl) : (Control)embeddedSearchControl);
		}

		private void GeneratePickerSelectionControlTree(Table table, int columnSpan)
		{
			TableRow row = new TableRow();
			table.Rows.Add(row);

			TableCell cell = new TableCell();
			row.Cells.Add(cell);
			cell.ColumnSpan = columnSpan;

			table = new Table();
			table.CssClass = "PickerSelectionDisplayTable";
			cell.Controls.Add(table);

			row = new TableRow();
			table.Rows.Add(row);

			cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(selectedPI);

			cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(selectedLab);

			cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(selectedCOS);
		}

		/// <summary>
		/// Get the list of extended search controls that are hosted by the same parent as our own.
		/// </summary>
		/// <returns>List of conrols</returns>
		public ArrayList GetExtendedSearchControls()
		{
			ArrayList controlList = new ArrayList();

			string extendedSearchControlNames = ExtendedSearchControl;
			if (extendedSearchControlNames != null)
			{
				string[] extendedSearchControls = extendedSearchControlNames.Trim().Split(',');
				foreach (string extendedSearchControlName in extendedSearchControls)
				{
					ISearch extendedSearchControl = Parent.FindControl(extendedSearchControlName) as ISearch;
					if (extendedSearchControl != null)
						controlList.Add(extendedSearchControl);
				}
			}
			return controlList;
		}

		/// <summary>
		/// Load the view-option drop-down-list with data.
		/// </summary>
		private void LoadViewOption()
		{
			if (!HideAllCommonControls && ViewOptionVisible)
			{
				DataSet ds = DataAccess.GetDataSet(DataAccess.ConnectionString, "GetViewOption",
					DataAccess.BuildSqlParameterArray("@module", SqlDbType.VarChar, Module));
				if (ds.Tables.Count == 1)
				{
					viewOption.DataTextField = "Name";
					viewOption.DataValueField = "Value";
					viewOption.DataSource = ds;
					viewOption.DataBind();
				}
			}
		}
		#endregion

		#region Internal Methods
		/// <summary>
		/// This method is ONLY invoked by the associated designer to show the control contents at design time.
		/// </summary>
		internal void Design()
		{
			EnsureChildControls();
		}
		#endregion
	}
	#endregion

	#region Class PagerDesigner
	/// <summary>
	/// Designer associated with the CommonSearch control.
	/// </summary>
	/// <remarks>
	/// This class cannot be used directly.
	/// </remarks>
	public class CommonSearchDesigner : ControlDesigner
	{
		#region Overrides
		/// <summary>
		/// Force the CommonSearch control to create its child controls for display at design time.
		/// </summary>
		/// <returns>HTML representation of the control</returns>
		public override string GetDesignTimeHtml()
		{
			((CommonSearch)Component).Design();
			return base.GetDesignTimeHtml();
		}
		#endregion
	}
	#endregion
}

// --- EOF ---
